{- |
Module      :  SudokuGrid.hs
Description :  Generic format for Sudoku grids
Copyright   :  (c) Michal Parusinski
License     :  GPLv3

Maintainer  :  mparusinski@googlemail.com
Stability   :  experimental
Portability :  portable

This module creates a data type called grid that is polymorphic.  It
is designed to handle to be flexible in size and to handle different
sets of symbol to allow easy customization.

It relies on arrays for fast access, and hopefully modifying the
structure in place, overall gaining in efficiency.

The absence of a symbol is handled by Nothing using the Maybe monad.

Arrays should always be created empty and handled using the exposed
function. Internals of Grid should never be accessed outside this
module and the SudokuGridUI module (for displaying the grid).  
-}

module SudokuGrid where

import qualified Data.Array as Array
import qualified Data.Maybe as Maybe
import qualified Data.List  as List 
import qualified Control.Monad as MonadCtrl

import qualified Symbols    as Symbols

data Grid symbol = Grid Int (Array.Array (Int,Int) (Maybe symbol))
                   deriving (Eq)

instance (Symbols.Symbol s) => Show (Grid s) where
    show = printGrid

printElem Nothing     = "_"
printElem (Just symb) = show symb

printGrid :: (Symbols.Symbol s) => Grid s -> String
-- Converts a grid into a string (DEBUGGING PURPOSES)
printGrid grid
    = let rawVersion = (List.intersperse delimiter rowStrings)
      in  MonadCtrl.join rawVersion
      where rowStrings = do 
              r <- getRows grid
              let strVer = printElem (head r) ++ concatMap strF (tail r) ++ "\n"
              return strVer
            rowLength  = (length . head) rowStrings 
            delimiter  = replicate rowLength '-' ++ "\n"
            strF x  = '|' : printElem x 

createEmptyGrid :: Int -> Grid symbol
createEmptyGrid size
    = let indicesList = [((i,j), Nothing) | i <- [1..size], j <- [1..size]]
      in Grid size $ Array.array ((1,1),(size,size)) indicesList

accessElements array
    = map ((Array.!) array)

getSize (Grid size _) = size

getRow (Grid size array) number
    = let indices = [(number, i) | i <- [1..size]]
      in accessElements array indices

getCol (Grid size array) number
    = let indices = [(i, number) | i <- [1..size]]
      in accessElements array indices

getBlock (Grid size array) number
    = let indices = [(i + iBase, j + jBase) | i <- [1..size], j <- [1..size]]
      in accessElements array indices
    where iBase = ((number-1) `mod` size) * size
          jBase = ((number-1) `div` size) * size

getRows grid
    = map (getRow grid) [1..(getSize grid)]

getCols grid
    = map (getCol grid) [1..(getSize grid)]

getBlocks grid
    = map (getBlock grid) [1..(getSize grid)]

getRowAt grid (i,_)
    = getRow grid i

getColAt grid (_,j)
    = getCol grid j

getBlockAt (Grid size array) (i,j)
    = let indices = [(i + iBase, j + jBase) | i <- [1..size], j <- [1..size]]
      in accessElements array indices
    where iBase = ((i-1) `div` size) * size
          jBase = ((j-1) `div` size) * size

getElemAt (Grid size array) (i,j)
    = array Array.! (i,j)

dumpElements (Grid size array)
    = Array.elems array

updateGrid (Grid size array) updateList
    = let newArray = array Array.// map reshape updateList
      in (Grid size newArray)
      where reshape (x,y) = (x, Maybe.Just y)

deleteFromGrid (Grid size array) positions
    = let newArray = array Array.// map reshape positions
      in (Grid size newArray)
      where reshape x = (x, Nothing)